{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Prepare 80 random bytes for small testing purpose"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "x = rand(UInt8, 8 * 10);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Define a function that converts 8 bytes into a single 64-bit unsigned integer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "f (generic function with 1 method)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f(a,b,c,d,e,f,g,h) = (UInt64(a) << 56) | (UInt64(b) << 48) | \n",
    "    (UInt64(c) << 40) | (UInt64(d) << 32) | \n",
    "    (UInt64(e) << 24) | (UInt64(f) << 16) | \n",
    "    (UInt64(g) << 8) | UInt64(h)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0xef91beb5eb863d46"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f(x[1],x[2],x[3],x[4],x[5],x[6],x[7],x[8])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1×8 RowVector{UInt8,Array{UInt8,1}}:\n",
       " 0xef  0x91  0xbe  0xb5  0xeb  0x86  0x3d  0x46"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x[1:8]'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Cool!  Let's apply the function evenly at 8-byte intervals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10-element Array{UInt64,1}:\n",
       " 0x77060c5e5d716cac\n",
       " 0x9cac05468f5d6e1f\n",
       " 0x0a307086c0144809\n",
       " 0x622368fae9938005\n",
       " 0xd4b10d1d565e756c\n",
       " 0xa72fc07832c524f4\n",
       " 0x166fe76b7f61a4b5\n",
       " 0x85004f6a1a9618d2\n",
       " 0xa463de4d3accf813\n",
       " 0xdb62335d7834ea9d"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f.(x[1:8:end],x[2:8:end],x[3:8:end],x[4:8:end],\n",
    "    x[5:8:end], x[6:8:end], x[7:8:end], x[8:8:end])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Now, let's create a much larger sample with 80,000 bytes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "N = 10000\n",
    "x = rand(UInt8, 8 * N);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "80000"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "length(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1×8 RowVector{UInt8,Array{UInt8,1}}:\n",
       " 0xe4  0x26  0x9a  0xef  0xd5  0xcd  0xe3  0x25"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "1×8 RowVector{UInt8,Array{UInt8,1}}:\n",
       " 0xe2  0x64  0xaa  0xa3  0x20  0xc4  0x1c  0xe5"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(x[1:8]')\n",
    "display(x[end-7:end]')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Moment of truth!  Let's do some benchmarks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "using BenchmarkTools"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Method 1 - vectorized function call"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  231.939 μs (157 allocations: 161.95 KiB)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3-element Array{UInt64,1}:\n",
       " 0xe4269aefd5cde325\n",
       " 0x3e8ea3affc7a7227\n",
       " 0x95e0716e61b4b55b"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y = @btime f.(x[1:8:end],x[2:8:end],x[3:8:end],x[4:8:end],\n",
    "    x[5:8:end], x[6:8:end], x[7:8:end], x[8:8:end]);\n",
    "y[1:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Method 2 - allocate an array and run a for-loop using reinterpret\n",
    "This takes longer and use more memory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  1.085 ms (40000 allocations: 2.59 MiB)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3-element Array{UInt64,1}:\n",
       " 0xe4269aefd5cde325\n",
       " 0x3e8ea3affc7a7227\n",
       " 0x95e0716e61b4b55b"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A = Array{UInt64}(N)\n",
    "function g(x, N, A) \n",
    "    z = 1\n",
    "    for i in 1:8:N*8\n",
    "        A[z] = bswap.(reinterpret(UInt64, x[i:i+8-1]))[1]\n",
    "        z += 1\n",
    "    end\n",
    "end\n",
    "@btime g(x, N, A)\n",
    "A[1:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Let's make an even bigger test case"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "N = 100_000\n",
    "x = rand(UInt8, 8 * N);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Method 1 - use array comprehension to slice the byte array and interpret"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  9.591 ms (300007 allocations: 18.31 MiB)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3-element Array{Float64,1}:\n",
       "  6.88627e-241\n",
       " -9.96849e-88 \n",
       "  1.85544e-14 "
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "function convertfloat(bytes)\n",
    "    values = [bytes[i:i+8-1] for i in 1:8:length(bytes)]\n",
    "    convertedvalues = map(x -> reinterpret(Float64, x)[1], values)\n",
    "    convertedvalues\n",
    "end\n",
    "y = @btime convertfloat(x);\n",
    "y[1:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Method 2 - create a simple reinterpret function and use vectorized call "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  9.608 ms (300032 allocations: 18.31 MiB)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3-element Array{Float64,1}:\n",
       "  6.88627e-241\n",
       " -9.96849e-88 \n",
       "  1.85544e-14 "
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "reinterpretF64(v) = reinterpret(Float64, v)[1]\n",
    "function convertfloat(bytes)\n",
    "    values = [bytes[i:i+8-1] for i in 1:8:length(bytes)]\n",
    "    reinterpretF64.(values)\n",
    "end\n",
    "y = @btime convertfloat(x);\n",
    "y[1:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### More serious performance comparison "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "convertint64"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Version a.  Original implementation... slow.\n",
    "\"\"\"\n",
    "Byte swap is needed only if file the array represent a different endianness\n",
    "than the system.  This function does not make any assumption and the caller\n",
    "is expected to pass `true` to the `swap` argument when needed.\n",
    "\"\"\"\n",
    "function convertfloat64a(bytes::Vector{UInt8}, swap::Bool)\n",
    "    values = [bytes[i:i+8-1] for i in 1:8:length(bytes)]\n",
    "    values = map(x -> reinterpret(Float64, x)[1], values)\n",
    "    swap ? bswap.(values) : values\n",
    "end\n",
    "\n",
    "# Version b.  Should be a lot faster.  \n",
    "\"\"\"\n",
    "It turns out that `reinterpret` consider a single UInt64 as BigEndian \n",
    "Hence it's necessary to swap bytes if the array is in LittleEndian convention.\n",
    "This function does not make any assumption and the caller\n",
    "is expected to pass `true` to the `swap` argument when needed.\n",
    "\"\"\"\n",
    "function convertfloat64b(bytes::Vector{UInt8}, endianess::Symbol) \n",
    "    v = endianess == :LittleEndian ? reverse(bytes) : bytes\n",
    "    c = convertint64.(v[1:8:end],v[2:8:end],v[3:8:end],v[4:8:end],\n",
    "            v[5:8:end], v[6:8:end], v[7:8:end], v[8:8:end])\n",
    "    r = reinterpret.(Float64, c)\n",
    "    endianess == :LittleEndian ? reverse(r) : r\n",
    "end\n",
    "\n",
    "\"\"\"\n",
    "Take 8 bytes and convert them into a UInt64 type.  The order is preserved.\n",
    "\"\"\"\n",
    "function convertint64(a::UInt8,b::UInt8,c::UInt8,d::UInt8,e::UInt8,f::UInt8,g::UInt8,h::UInt8)\n",
    "    (UInt64(a) << 56) | (UInt64(b) << 48) | \n",
    "    (UInt64(c) << 40) | (UInt64(d) << 32) | \n",
    "    (UInt64(e) << 24) | (UInt64(f) << 16) | \n",
    "    (UInt64(g) << 8)  |  UInt64(h)\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3-element Array{Float64,1}:\n",
       "  6.88627e-241\n",
       " -9.96849e-88 \n",
       "  1.85544e-14 "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "3-element Array{Float64,1}:\n",
       " 1.47201e-41 \n",
       " 3.92903e-70 \n",
       " 2.77759e-145"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  0.254849 seconds (300.01 k allocations: 18.311 MiB, 78.50% gc time)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(100000,)"
      ]
     },
     "execution_count": 74,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "@time z1 = convertfloat64a(x, false)\n",
    "display(z1[1:3])\n",
    "display(z1[end-2:end])\n",
    "size(z1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3-element Array{Float64,1}:\n",
       "  6.88627e-241\n",
       " -9.96849e-88 \n",
       "  1.85544e-14 "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "3-element Array{Float64,1}:\n",
       " 1.47201e-41 \n",
       " 3.92903e-70 \n",
       " 2.77759e-145"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  0.011477 seconds (44 allocations: 3.817 MiB)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(100000,)"
      ]
     },
     "execution_count": 75,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "@time z2 = convertfloat64b(x, :LittleEndian)\n",
    "display(z2[1:3])\n",
    "display(z2[end-2:end])\n",
    "size(z2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Julia 0.6.1",
   "language": "julia",
   "name": "julia-0.6"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "0.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
